﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace Common
{
    /// <summary>
    /// A statistics gathering tool to give periodic reports about which value is the most popular (for cache optimization)
    /// </summary>
    /// <typeparam name="TValue">The value type</typeparam>
    /// <typeparam name="TState">The type of meta data associated with the value</typeparam>
    public class MostPopularObjectTracker<TValue, TState>
    {
        private object synclock = new object();

        private IDictionary<TValue, StatsTracker<TValue, TState>> lookup = new Dictionary<TValue, StatsTracker<TValue, TState>>();
        private IList<StatsTracker<TValue, TState>> allStats = new List<StatsTracker<TValue, TState>>();

        /// <summary>
        /// Votes a value up
        /// </summary>
        /// <param name="obj">The value to be voted</param>
        /// <param name="state">A state object to associate with the value iff the value was not already being tracked, otherwise this is ignored</param>
        public void VoteUp(TValue obj, TState state)
        {
            lock (synclock)
            {
                StatsTracker<TValue, TState> st = null;
                if (!lookup.TryGetValue(obj, out st) || st == null)
                {
                    st = new StatsTracker<TValue, TState>() { Value = obj, State = state };
                    lookup.Add(obj, st);
                    allStats.Add(st);
                }
                st.Votes++;
            }
        }

        /// <summary>
        /// Discards all values currently being tracked except for the 'n' most popular values
        /// </summary>
        /// <param name="numToKeep">The number of popular values to keep</param>
        public void PruneData(int numToKeep)
        {
            lock (synclock)
            {
                var keeping = MostPopular(numToKeep);
                allStats.Clear();
                lookup.Clear();
                foreach (var kept in keeping)
                {
                    VoteUp(kept.Value, kept.State);
                }
            }
        }

        /// <summary>
        /// Returns the 'n' most popular values
        /// </summary>
        /// <param name="numberToGrab">The number of values to return</param>
        /// <returns>The 'n' most popular values</returns>
        public IEnumerable<StatsTracker<TValue, TState>> MostPopular(int numberToGrab)
        {
            lock (synclock)
            {
                return (from stat in allStats
                        orderby stat.Votes ascending
                        select stat).Take(numberToGrab).ToList();
            }
        }

        [ContractInvariantMethod]
        private void InvariantMethod()
        {
            Contract.Invariant(allStats != null);
            Contract.Invariant(lookup != null);
        }
    }

    /// <summary>
    /// Stores the statistics data for a given value
    /// </summary>
    public class StatsTracker<TValue, TState>
    {
        /// <summary>
        /// The value that was tracked
        /// </summary>
        public TValue Value { get; internal set; }
        /// <summary>
        /// The number of times the value was voted for
        /// </summary>
        internal int Votes { get; set; }
        /// <summary>
        /// Some meta data that was associated with the value when it was first recorded
        /// </summary>
        public TState State { get; internal set; }
    }
}
